package com.example.distributelock.lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;

import java.util.Collections;
import java.util.List;
import java.util.UUID;

// AutoCloseable是jdk1.7版本推出的自动关闭的接口，实现这个接口重写close()方法在close()方法中关闭锁即可直接在try()的()中获取锁并释放锁不用在finally中手动释放锁，
// 解决了可能由于个人原因忘记写finally而忘记释放锁的问题
public class RedisLock implements AutoCloseable {

    public static final Logger log = LoggerFactory.getLogger(RedisLock.class);

    private final RedisTemplate redisTemplate;
    private final String key;
    private final String value;
    // 单位：秒
    private final int expireTime;

    public RedisLock(RedisTemplate redisTemplate, String key, int expireTime){
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.expireTime = expireTime;
        this.value = UUID.randomUUID().toString();
    }

    /**
     * 获取分布式锁
     * @return
     */
    public boolean getLock() {
        RedisCallback<Boolean> redisCallback = connection -> {
            // 设置NX
            RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
            // 设置过期时间
            Expiration expiration = Expiration.seconds(expireTime);
            // 序列化key
            byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
            // 序列化value
            byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
            // 执行setnx操作
            Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
            return Boolean.TRUE.equals(result);
        };

        // 获取分布式锁
        Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
        return Boolean.TRUE.equals(lock);
    }

    public void unLock() {
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
        List<String> keys = Collections.singletonList(key);

        Boolean result = (Boolean) redisTemplate.execute(redisScript, keys, value);
        log.info("释放锁的结果：" + result);
    }


    @Override
    public void close() {
        unLock();
    }

}
